package cn.wch.ch934xlib.connet;

import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbManager;

import androidx.annotation.NonNull;

import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Locale;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import cn.wch.ch934xlib.assist.GPIOAssist;
import cn.wch.ch934xlib.assist.ModeAssist;
import cn.wch.ch934xlib.chip.ChipFeature;
import cn.wch.ch934xlib.chip.ChipParameterUtil;
import cn.wch.ch934xlib.chip.ChipEntity;
import cn.wch.ch934xlib.chip.ChipInitUtil;
import cn.wch.ch934xlib.assist.ReceiveDataAssist;
import cn.wch.ch934xlib.assist.WriteDataAssist;
import cn.wch.ch934xlib.exception.ChipException;
import cn.wch.ch934xlib.exception.UartLibException;
import cn.wch.ch934xlib.gpio.GPIO_DIR;
import cn.wch.ch934xlib.gpio.GPIO_VALUE;
import cn.wch.ch934xlib.utils.FormatUtil;
import cn.wch.ch934xlib.utils.LogUtil;


public final class Connection implements ReadTask.DataCallback, ReceiveDataAssist.ISerialDataCallback {
    private final UsbManager usbManager;
    private final UsbDevice usbDevice;
    private final ChipEntity chipEntity;
    private UsbDeviceConnection usbDeviceConnection;
    private ThreadPoolExecutor threadPoolExecutor;

    private final HashMap<UsbEndpoint, ReadTask> readMap;
    private boolean isAlive=false;
    private final HashMap<Integer, ByteBuffer> bufferMap;
    private final int BUFFER_SIZE=1024*70;
    private final Lock lock=new ReentrantLock(true);

    private int serialCount=0;
    private int gpioCount=12;
    private ChipFeature feature;
    private ReceiveDataAssist receiveDataAssist;
    private WriteDataAssist writeDataAssist;

    public Connection(@NonNull UsbManager usbManager, @NonNull UsbDevice usbDevice, @NonNull ChipEntity chipEntity) {
        this.usbDevice = usbDevice;
        this.chipEntity = chipEntity;
        this.usbManager=usbManager;
        readMap=new HashMap<>();
        bufferMap=new HashMap<>();
        receiveDataAssist =new ReceiveDataAssist();
        writeDataAssist =new WriteDataAssist();
    }

    public void init()throws ChipException {
        //打开设备
        usbDeviceConnection = usbManager.openDevice(usbDevice);
        if(usbDeviceConnection==null){
            throw new ChipException("open device fail");
        }
        //独占接口
        if(!usbDeviceConnection.claimInterface(chipEntity.getUsbInterface(),true)){
            throw new ChipException("claimInterface fail");
        }
        //获取串口特性
        feature = ChipInitUtil.getChipFeature(usbDeviceConnection);
        if(feature==null){
            throw new ChipException("get chip feature fail");
        }
        serialCount=feature.getSerialCount();
        if(serialCount!=2 && serialCount!=4){
            throw new ChipException(String.format(Locale.getDefault(),"get incorrect chip serial count：%d", serialCount));
        }
        LogUtil.d(String.format(Locale.getDefault(),"当前芯片串口数目：%d",serialCount));
        //初始化流控记录
        ModeAssist.getInstance().init(usbDevice,serialCount);

        //初始化GPIO记录
        if(serialCount==4){
            gpioCount=12;
        }
        GPIOAssist.getInstance().init(usbDevice,serialCount,gpioCount);
        //初始化芯片
        if(!ChipInitUtil.initChip(chipEntity,usbDeviceConnection,serialCount,feature.isSupportMode9Line())){
            throw new ChipException("init chip fail");
        }
        LogUtil.d("初始化芯片成功");
        //根据串口数目新建缓冲区存放接收到的数据
        initByteBuffers(serialCount);
        //数据处理
        receiveDataAssist.setSerialDataCallback(this);
        //数据发送状态
        writeDataAssist.init(serialCount);
        //新建线程池
        threadPoolExecutor = new ThreadPoolExecutor(4, 4+5, 5, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(8), Executors.defaultThreadFactory(), new RejectedExecutionHandler() {
            @Override
            public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                LogUtil.d("线程池拒绝了");
            }
        });
        //新建读任务(共2个)
        ReadTask readTask0=new ReadTask(chipEntity.getChipType(),usbDeviceConnection, chipEntity.getCtrlEpIn());
        readTask0.setDataCallback(this);
        readMap.put(chipEntity.getCtrlEpIn(),readTask0);
        threadPoolExecutor.execute(readTask0);

        ReadTask readTask1=new ReadTask(chipEntity.getChipType(),usbDeviceConnection, chipEntity.getBulkEpIn());
        readTask1.setDataCallback(this);
        readMap.put(chipEntity.getBulkEpIn(),readTask1);
        threadPoolExecutor.execute(readTask1);

        isAlive=true;

    }

    private void initByteBuffers(int serialCount) {
        for (int i = 0; i < serialCount; i++) {
            bufferMap.put(i,ByteBuffer.allocate(BUFFER_SIZE));
        }
    }


    public boolean setSerialParameter(int serialNumber, int baud,
                                      int dataBit, int stopBit, int parityBit, boolean flow) throws ChipException {

        //检查串口号
        if(serialNumber<0||serialNumber>=serialCount){
            throw new ChipException(String.format("this chip only has %d serial", serialCount));
        }
        return ChipParameterUtil.setGeneralBaudRate(usbDevice,usbDeviceConnection,serialNumber,baud,dataBit,stopBit,parityBit,flow,chipEntity,serialCount);
    }


    public int write(int serialNumber,byte[] data,int length,int waitStateTimeout,int timeout) throws ChipException {
        int total=0;
        if(usbDeviceConnection==null){
            throw new ChipException("device isn't opened");
        }
        if(data==null || data.length<length){
            throw new ChipException("the function parameter data and length are invalid");
        }
        int maxPacketSize = chipEntity.getBulkEpOut().getMaxPacketSize();
        //减去3个字节的帧头
        int maxDataSize=maxPacketSize-3;
        int realLen=Math.min(data.length,length);
        int fullCount=realLen/maxDataSize;
        for(int i=0;i<fullCount;i++){
            byte[] tmp=new byte[maxPacketSize];

            tmp[0] = (byte) (serialNumber+4);
            tmp[1]= (byte)(maxPacketSize & 0xff);
            tmp[2]=(byte)((maxPacketSize>>8)& 0xff);

            System.arraycopy(data,i*maxPacketSize,tmp,3,maxDataSize);
            if(!write(serialNumber, tmp,waitStateTimeout, timeout)) {
                return total;
            }
            total+=maxDataSize;
            if(i==(fullCount-1) && data.length%maxDataSize==0){
                break;
            }
        }
        int res = realLen % maxDataSize;
        byte[] tmp=new byte[res+3];
        if(tmp.length!=0) {
            tmp[0] = (byte) (serialNumber+4);
            tmp[1]= (byte)(res & 0xff);
            tmp[2]=(byte)((res>>8)& 0xff);

            System.arraycopy(data, fullCount * maxDataSize, tmp, 3, res);
            if(!write(serialNumber, tmp,waitStateTimeout, timeout)) {
                return total;
            }
            total+=res;
        }
        return total;
    }

    private boolean write(int serialNumber,byte[] data,int waitStateTimeout,int writeTimeout){
        LogUtil.d("尝试发送："+FormatUtil.bytesToHexString(data,data.length));
        if(!writeDataAssist.waitReady(serialNumber, waitStateTimeout)){
            LogUtil.d("等待缓冲区空标志失败");
            return false;
        }
        LogUtil.d("等待缓冲区空标志成功");
        writeDataAssist.setState(serialNumber,false);

        int i = usbDeviceConnection.bulkTransfer(chipEntity.getBulkEpOut(), data, data.length, writeTimeout);
        if(i==data.length){
            LogUtil.d("发送成功");
            return true;
        }
        LogUtil.d("发送失败");
        return false;
    }

    @Override
    public void onData(UsbEndpoint endpoint, byte[] buffer, int length) {

        //LogUtil.d(String.format(Locale.getDefault(),"端点%d(%d)收到：%s",endpoint.getEndpointNumber(),length, FormatUtil.bytesToHexString(buffer,length)));

        if(endpoint.equals(chipEntity.getBulkEpIn())){
            receiveDataAssist.put(buffer,length);
        }else if(endpoint.equals(chipEntity.getCtrlEpIn())){
            writeDataAssist.setState(buffer,length);
        }
    }


    @Override
    public void onSerialData(int serialNumber, byte[] buffer, int length) {
        LogUtil.d(String.format(Locale.getDefault(),"串口%d(%d)收到：%s",serialNumber,length, FormatUtil.bytesToHexString(buffer,length)));
        putDataIntoBuffer(serialNumber,buffer,length);
    }

    private void putDataIntoBuffer(int serialNumber, byte[] buffer, int length){
        ByteBuffer byteBuffer = bufferMap.get(serialNumber);
        if(byteBuffer==null){
            LogUtil.e("未找到对应的设备缓冲区");
            return;
        }
        try {
            lock.lock();
            if(byteBuffer.remaining()==0){

                byteBuffer.clear();
                LogUtil.e("缓冲区没有足够空间，清除缓冲区");
            }
            if(byteBuffer.remaining()>=length){
                //缓冲区有足够空间
                byteBuffer.put(buffer,0,length);
            }else {
                //缓冲区没有足够空间,超出的部分放弃存入
                byteBuffer.put(buffer,0,byteBuffer.remaining());
                LogUtil.e("缓冲区没有足够空间，超出的部分放弃存入");
            }
        }finally {
            lock.unlock();
        }
    }

    public byte[] readDataFromBuffer(int serialNumber){
        ByteBuffer byteBuffer = bufferMap.get(serialNumber);
        if(byteBuffer==null){
            LogUtil.d("未找到对应的设备缓冲区");
            return null;
        }
        if(byteBuffer.position()==0){
            return null;
        }
        byte[] dst=null;
        try {
            lock.lock();
            int position = byteBuffer.position();
            dst=new byte[position];
            byteBuffer.flip();
            byteBuffer.get(dst);
            byteBuffer.compact();
        }finally {
            lock.unlock();
        }
        return dst;
    }

    public byte[] readDataFromBuffer(int serialNumber,int readCount,int timeout){
        ByteBuffer byteBuffer = bufferMap.get(serialNumber);
        if(byteBuffer==null){
            LogUtil.d("未找到对应的设备缓冲区");
            return null;
        }
        byte[] dst=null;
        while (timeout-->0){

            if(byteBuffer.position()>=readCount){
                //有足够的数据
                try {
                    lock.lock();
                    int position = byteBuffer.position();
                    dst=new byte[position];
                    byteBuffer.flip();
                    byteBuffer.get(dst);
                    byteBuffer.compact();
                }finally {
                    lock.unlock();
                }
                break;
            }else {
                //没有足够的数据
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        return dst;
    }

    public boolean setFlow(int serialNumber,boolean enableFlow){
        return ChipParameterUtil.setFlowEnabled(usbDevice,usbDeviceConnection,chipEntity.getCtrlEpOut(),
                serialNumber,enableFlow,serialCount);
    }

    public boolean enableGPIO(int serialNumber,boolean enabled) throws UartLibException {
        return ChipParameterUtil.setGPIOEnabled(usbDevice,usbDeviceConnection,chipEntity.getCtrlEpOut(),serialNumber,enabled,serialCount);
    }


    public boolean setGPIODir(int gpioNumber, @NonNull GPIO_DIR dir) throws UartLibException {
        return ChipParameterUtil.setGPIODir(usbDevice,usbDeviceConnection,chipEntity.getCtrlEpOut(),gpioNumber,dir,gpioCount);
    }

    public boolean setGPIOValue(int gpioNumber, @NonNull GPIO_VALUE value) throws UartLibException {
        return ChipParameterUtil.setGPIOValue(usbDevice,usbDeviceConnection,chipEntity.getCtrlEpOut(),gpioNumber,value,gpioCount);
    }

    public boolean getGPIOValue() throws UartLibException {
        return ChipParameterUtil.getGPIOValue(usbDevice,usbDeviceConnection,chipEntity.getCtrlEpOut(),gpioCount,2000);
    }

    public GPIO_VALUE queryGPIOValueFromCache(int gpioNumber) throws UartLibException {
        return ChipParameterUtil.queryGPIOValueFromCache(usbDevice,gpioNumber,gpioCount);
    }


    public void close(){
        //关闭读任务
        for (ReadTask readTask : readMap.values()) {
            if(readTask!=null){
                readTask.stop();
            }
        }
        //关闭线程池
        threadPoolExecutor.shutdown();
        //释放资源
        usbDeviceConnection.close();
        //标志置false
        isAlive=false;
        //关闭串口缓冲区标志
        writeDataAssist.close();
    }

    public boolean isAlive() {
        return isAlive;
    }

    public UsbManager getUsbManager() {
        return usbManager;
    }

    public UsbDevice getUsbDevice() {
        return usbDevice;
    }

    public ChipEntity getChipEntity() {
        return chipEntity;
    }

    public UsbDeviceConnection getUsbDeviceConnection() {
        return usbDeviceConnection;
    }

    public int getSerialCount() {
        return serialCount;
    }
}
